#include <JXQCore/JXQGlobal>
#if defined(J_OS_UNIX) || defined(J_OS_LINUX)

#include "jserialport.h"
#include <JXQCore/JMutex>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/select.h>

namespace JXQ
{

typedef JSerialPort::StopBit       StopBit;
typedef JSerialPort::CheckMode     CheckMode;
typedef JSerialPort::DataBitsType  DataBitsType;
typedef JSerialPort::FlowType      FlowType;

class J_DECL_HIDDEN JSerialPortPrivate
{
public:
	JSerialPortPrivate(JSerialPort *q_ptr);

public:
	void setBaudRate(uint32_t baudRate);
	void setStopBit(StopBit sb);
	void setCheckMode(CheckMode mode);
	void setDataBitsType(DataBitsType type);
	void setFlowControl(FlowType type);
	void set();

public:
	JSerialPort *q_ptr;
	std::mutex mutex;
	struct termios Posix_CommConfig;

	uint32_t baudRate     = JSerialPort::BR_9600;
	StopBit stopBits      = JSerialPort::SB_1Bit;
	CheckMode checkMode   = JSerialPort::CM_None;
	DataBitsType dataBits = JSerialPort::DBT_8;
	FlowType flowControl     = JSerialPort::Flow_Off;
};

JSerialPortPrivate::JSerialPortPrivate(JSerialPort *q_ptr) :
	q_ptr(q_ptr)
{

}

void JSerialPortPrivate::setBaudRate(uint32_t br)
{
	J_MUTEX_LOCKER(mutex);
	baudRate = br;

	if( not q_ptr->isOpen() )
		return ;

#ifdef CBAUD
	Posix_CommConfig.c_cflag &= ~CBAUD;
	if( baudRate == JSerialPort::BR_50 )
		Posix_CommConfig.c_cflag |= B50;
	else if( baudRate == JSerialPort::BR_75 )
		Posix_CommConfig.c_cflag |= B75;
	else if( baudRate == JSerialPort::BR_110 )
		Posix_CommConfig.c_cflag |= B110;
	else if( baudRate == JSerialPort::BR_150 )
		Posix_CommConfig.c_cflag |= B150;
	else if( baudRate == JSerialPort::BR_200 )
		Posix_CommConfig.c_cflag |= B200;
	else if( baudRate == JSerialPort::BR_300 )
		Posix_CommConfig.c_cflag |= B300;
	else if( baudRate == JSerialPort::BR_600 )
		Posix_CommConfig.c_cflag |= B600;
	else if( baudRate == JSerialPort::BR_1200 )
		Posix_CommConfig.c_cflag |= B1200;
	else if( baudRate == JSerialPort::BR_1800 )
		Posix_CommConfig.c_cflag |= B1800;
	else if( baudRate == JSerialPort::BR_2400 )
		Posix_CommConfig.c_cflag |= B2400;
	else if( baudRate == JSerialPort::BR_4800 )
		Posix_CommConfig.c_cflag |= B4800;
	else if( baudRate == JSerialPort::BR_9600 )
		Posix_CommConfig.c_cflag |= B9600;
	else if( baudRate == JSerialPort::BR_19200 )
		Posix_CommConfig.c_cflag |= B19200;
	else if( baudRate == JSerialPort::BR_38400 )
		Posix_CommConfig.c_cflag |= B38400;
	else if( baudRate == JSerialPort::BR_57600 )
		Posix_CommConfig.c_cflag |= B57600;
	else if( baudRate == JSerialPort::BR_115200 )
		Posix_CommConfig.c_cflag |= B115200;
#else
	switch(baudRate)
	{
		case JSerialPort::BR_50:
			cfsetispeed(&Posix_CommConfig, B50);
			cfsetospeed(&Posix_CommConfig, B50);
			break;

		case JSerialPort::BR_75:
			cfsetispeed(&Posix_CommConfig, B75);
			cfsetospeed(&Posix_CommConfig, B75);
			break;

		case JSerialPort::BR_110:
			cfsetispeed(&Posix_CommConfig, B110);
			cfsetospeed(&Posix_CommConfig, B110);
			break;

		case JSerialPort::BR_150:
			cfsetispeed(&Posix_CommConfig, B150);
			cfsetospeed(&Posix_CommConfig, B150);
			break;

		case JSerialPort::BR_200:
			cfsetispeed(&Posix_CommConfig, B200);
			cfsetospeed(&Posix_CommConfig, B200);
			break;

		case JSerialPort::BR_300:
			cfsetispeed(&Posix_CommConfig, B300);
			cfsetospeed(&Posix_CommConfig, B300);
			break;

		case JSerialPort::BR_600:
			cfsetispeed(&Posix_CommConfig, B600);
			cfsetospeed(&Posix_CommConfig, B600);
			break;

		case JSerialPort::BR_1200:
			cfsetispeed(&Posix_CommConfig, B1200);
			cfsetospeed(&Posix_CommConfig, B1200);
			break;

		case JSerialPort::BR_1800:
			cfsetispeed(&Posix_CommConfig, B1800);
			cfsetospeed(&Posix_CommConfig, B1800);
			break;

		case JSerialPort::BR_2400:
			cfsetispeed(&Posix_CommConfig, B2400);
			cfsetospeed(&Posix_CommConfig, B2400);
			break;

		case JSerialPort::BR_4800:
			cfsetispeed(&Posix_CommConfig, B4800);
			cfsetospeed(&Posix_CommConfig, B4800);
			break;

		case JSerialPort::BR_9600:
			cfsetispeed(&Posix_CommConfig, B9600);
			cfsetospeed(&Posix_CommConfig, B9600);
			break;

		case JSerialPort::BR_19200:
			cfsetispeed(&Posix_CommConfig, B19200);
			cfsetospeed(&Posix_CommConfig, B19200);
			break;

		case JSerialPort::BR_38400:
			cfsetispeed(&Posix_CommConfig, B38400);
			cfsetospeed(&Posix_CommConfig, B38400);
			break;

		case JSerialPort::BR_57600:
			cfsetispeed(&Posix_CommConfig, B57600);
			cfsetospeed(&Posix_CommConfig, B57600);
			break;

		case JSerialPort::BR_115200:
			cfsetispeed(&Posix_CommConfig, B115200);
			cfsetospeed(&Posix_CommConfig, B115200);
			break;
	}
#endif
	tcsetattr(q_ptr->handle(), TCSAFLUSH, &Posix_CommConfig);
	J_MUTEX_LOCKER_UNLOCK();
}

void JSerialPortPrivate::setStopBit(StopBit sb)
{
	J_MUTEX_LOCKER(mutex);
	stopBits = sb;

	if( not q_ptr->isOpen() )
		return ;

	switch(sb)
	{
		case JSerialPort::SB_1Bit:
			Posix_CommConfig.c_cflag &= ~CSTOPB;
			break;

		case JSerialPort::SB_1p5Bit:
			jError() << "JSerialPort: 1.5 stop bit operation is not supported by POSIX.";
			break;

		case JSerialPort::SB_2Bit:
			if( dataBits == JSerialPort::DBT_5 )
				jError() << "JSerialPort: 2 stop bits cannot be used with 5 data bits";
			else
			{
				stopBits = sb;
				Posix_CommConfig.c_cflag |= CSTOPB;
			}
			break;
	}

	tcsetattr(q_ptr->handle(), TCSAFLUSH, &Posix_CommConfig);
	J_MUTEX_LOCKER_UNLOCK();
}

void JSerialPortPrivate::setCheckMode(CheckMode mode)
{
	J_MUTEX_LOCKER(mutex);
	if( not q_ptr->isOpen() )
		return ;

	checkMode = mode;
	switch(mode)
	{
		case JSerialPort::CM_None:
			Posix_CommConfig.c_cflag &= ~PARENB;
			break;

		case JSerialPort::CM_Even:
			Posix_CommConfig.c_cflag &= ~PARODD;
			Posix_CommConfig.c_cflag |= PARENB;
			break;

		case JSerialPort::CM_Odd:
			Posix_CommConfig.c_cflag |= PARENB | PARODD;
			break;

		case JSerialPort::CM_SPACE:
			if( dataBits == JSerialPort::DBT_8 )
				jError() << "JSerialPort: Space parity is only supported in POSIX with 7 or fewer data bits";
			else
			{
				Posix_CommConfig.c_cflag &= ~(PARENB | CSIZE);
				switch(dataBits)
				{
					case JSerialPort::DBT_5:
						dataBits = JSerialPort::DBT_6;
						Posix_CommConfig.c_cflag |= CS6;
						break;

					case JSerialPort::DBT_6:
						dataBits = JSerialPort::DBT_7;
						Posix_CommConfig.c_cflag |= CS7;
						break;

					case JSerialPort::DBT_7:
						dataBits = JSerialPort::DBT_8;
						Posix_CommConfig.c_cflag |= CS8;
						break;

					default: break;
				}
			}
			break;
	}

	tcsetattr(q_ptr->handle(), TCSAFLUSH, &Posix_CommConfig);
	J_MUTEX_LOCKER_UNLOCK();
}

void JSerialPortPrivate::setDataBitsType(DataBitsType type)
{
	J_MUTEX_LOCKER(mutex);
	dataBits = type;
	Posix_CommConfig.c_cflag &= ~CSIZE;

	if( not q_ptr->isOpen() )
		return ;

	switch(type)
	{
		case JSerialPort::DBT_5:
			if( stopBits != JSerialPort::SB_2Bit )
				jError() << "JSerialPort: 5 Data bits cannot be used with 2 stop bits.";
			else
				Posix_CommConfig.c_cflag |= CS5;
			break;

		case JSerialPort::DBT_6:
			if( stopBits != JSerialPort::SB_1p5Bit )
				jError() << "Posix_QextSerialPort: 6 Data bits cannot be used with 1.5 stop bits.";
			else
				Posix_CommConfig.c_cflag |= CS6;
			break;

		case JSerialPort::DBT_7:
			if( stopBits == JSerialPort::SB_1p5Bit )
				jError() << "Posix_QextSerialPort: 7 Data bits cannot be used with 1.5 stop bits.";
			else
				Posix_CommConfig.c_cflag |= CS7;
			break;

		case JSerialPort::DBT_8:
			Posix_CommConfig.c_cflag |= CS8;
			break;
	}

	tcsetattr(q_ptr->handle(), TCSAFLUSH, &Posix_CommConfig);
	J_MUTEX_LOCKER_UNLOCK();
}

void JSerialPortPrivate::setFlowControl(FlowType type)
{
	J_MUTEX_LOCKER(mutex);
	flowControl = type;

	if( not q_ptr->isOpen() )
		return ;

	switch(type)
	{
		case JSerialPort::Flow_Off:
			Posix_CommConfig.c_cflag &= ~CRTSCTS;
			Posix_CommConfig.c_iflag &= ~(IXON | IXOFF | IXANY);
			break;

		case JSerialPort::Flow_XonxOff:
			Posix_CommConfig.c_cflag &= ~CRTSCTS;
			Posix_CommConfig.c_iflag |= IXON | IXOFF | IXANY;
			break;

		case JSerialPort::Flow_Hardware:
			Posix_CommConfig.c_cflag |= CRTSCTS;
			Posix_CommConfig.c_iflag &= ~(IXON | IXOFF | IXANY);
			break;
	}

	tcsetattr(q_ptr->handle(), TCSAFLUSH, &Posix_CommConfig);
	J_MUTEX_LOCKER_UNLOCK();
}

void JSerialPortPrivate::set()
{
	setBaudRate(baudRate);
	setStopBit(stopBits);
	setCheckMode(checkMode);
	setDataBitsType(dataBits);
	setFlowControl(flowControl);
}

/*------------------------------------------------------------------------*/

JSerialPort::JSerialPort() :
	d_ptr(new JSerialPortPrivate(this))
{

}

JSerialPort::JSerialPort(const JString &comName) :
	JBus(comName),
	d_ptr(new JSerialPortPrivate(this))
{

}

JSerialPort::~JSerialPort()
{
	delete d_ptr;
}

void JSerialPort::setBaudRate(uint32_t baudRate)
{
	d_ptr->setBaudRate(baudRate);
}

uint32_t JSerialPort::baudRate() const
{
	return d_ptr->baudRate;
}

void JSerialPort::setStopBit(StopBit sb)
{
	d_ptr->setStopBit(sb);
}

StopBit JSerialPort::stopBit() const
{
	return d_ptr->stopBits;
}

void JSerialPort::setCheckMode(CheckMode mode)
{
	d_ptr->setCheckMode(mode);
}

CheckMode JSerialPort::checkMode() const
{
	return d_ptr->checkMode;
}

void JSerialPort::setDataBitsType(DataBitsType type)
{
	d_ptr->setDataBitsType(type);
}

DataBitsType JSerialPort::setDataBitsType() const
{
	return d_ptr->dataBits;
}

void JSerialPort::setFlowControl(FlowType type)
{
	d_ptr->setFlowControl(type);
}

FlowType JSerialPort::flowControl() const
{
	return d_ptr->flowControl;
}

bool JSerialPort::open(OpenMode mode)
{
	if( JBus::open(mode) )
	{
		d_ptr->set();
		return true;
	}
	return false;
}

bool JSerialPort::open(int handle, OpenMode mode)
{
	if( JBus::open(handle, mode) )
	{
		d_ptr->set();
		return true;
	}
	return false;
}

bool JSerialPort::open(const FILE *fsp, OpenMode mode)
{
	if( JBus::open(fsp, mode) )
	{
		d_ptr->set();
		return true;
	}
	return false;
}

} // namespace JXQ

#endif
